#ifndef MESHTYPEFUNCTIONS_HPP
#define MESHTYPEFUNCTIONS_HPP

#include "meshTypes_fwd.hpp"
#include "meshTypes.hpp"
#include <iostream>
#include <utility>
#include <optional>

namespace hms
{

template<typename MeshType>
constexpr bool isStructMeshBase(){
	return std::is_base_of_v<StructMeshBase<MeshType>, MeshType>;
}

template<typename MeshType>
MeshType uniMeshAs(
	Index  nCellsX,
	Index  nCellsY,
	scalar dx,
	scalar dy,
	bool   addBoundaryPatches = false,
	scalar offsetX = 0,
	scalar offsetY = 0
);

template<typename MeshType>
MeshType uniMeshAs(
	Array2i nCells,
	Array2s diff,
	Array2s orig = {0, 0},
	bool addBoundaryPatches = false
);



/**
 * @brief Returns the unique x- and y-coordinates from a set of vertices
 * 
 * @param vertices 
 * @return std::pair<VectorXs, VectorXs> .first are the x- and .second are the
 * y-coordinates
 */
auto uniqueCoordinates( const Ref<const Matrix2Xs>& vertices) ->
	std::pair<VectorXs, VectorXs>;

/**
 * @brief Checks whether all domain cells of a mesh are quadrilaterals
 * 
 * @return true if all domain cells have four edges
 * @return false if at least one domain cell is a triangle
 */
bool isQuadrilateral( const Mesh& );



/**
 * @brief Tests whether a mesh has regular topology.
 * 
 * @return true 
 * @return false 
 * 
 * The test it performs is whether or not all vertices which do not lie on the
 * boundary have four edges attached to them. Vertices on the boundary must have
 * either three edges attached to them or two, in which case that vertex is a 
 * corner. There must be exactly four corners.
 * Calls isQuadrilateral as a first test.
 */
auto isStructured( const Mesh&, const std::vector<IdxVector>& = {} ) ->
	std::optional<IdxVector>;



/**
 * @brief Tests whether a mesh has only rectangular cells.
 * @overload
 * 
 * Retrieves the unique x- and y-coordinates and checks whether their product
 * matches the number of domain cells. Calls isStructured first.
 */
template<typename MeshType>
bool isRectangular( const MeshBase<MeshType>& mesh );

/** @overload */
template<typename MeshType>
bool isRectangular(
	const MeshBase<MeshType>& mesh,
	const Ref<const VectorXs>& xCoords,
	const Ref<const VectorXs>& yCoords,
	const std::vector<IdxVector>& edgesAtVertices = {} );

/**
 * @brief Tests whether the given unique x- and y-coordinates' sizes result
 * in the correct number of cells to form a rectangular mesh
 * 
 * @param nDomainCells 
 * @param xCoords unique x-coordinates of a mesh (obtained by calling 
 * uniqueCoordinates())
 * @param yCoords unique y-coordinates of a mesh
 * @return true 
 * @return false 
 * 
 * This is the function called by all overloads and by optimiseMesh(). Does not
 * chain to isStructured().
 */
bool isRectangular(
	Index nDomainCells,
	const Ref<const VectorXs>& xCoords,
	const Ref<const VectorXs>& yCoords );



/**
 * @brief Tests whether given x- and y-coordinates are evenly spaced and thus
 * form a uniform mesh.
 * 
 * @param xCoords unique x-coordinates of a mesh (obtained by calling 
 * uniqueCoordinates())
 * @param yCoords unique y-coordinates of a mesh
 * @return true 
 * @return false 
 * 
 * This is the one called by all overloads and optimiseMesh(). Does not chain to
 * isRectangular().
 */
bool isUniform(
	const Ref<const VectorXs>& xCoords,
	const Ref<const VectorXs>& yCoords );

/**
 * @brief Tests whether a mesh consists of uniform rectangular cells.
 * @overload
 * 
 * @tparam MeshType must be derived from MeshBase
 * @param mesh 
 * Provided for testing purposes, so that only a mesh is required as a 
 * parameter. Chains call to
 * isUniform(
	const MeshBase<MeshType>&,
	const Ref<const VectorXs>&,
	const Ref<const VectorXs>&,
	const std::vector<IdxVector>&)
 */
template<typename MeshType>
bool isUniform( const MeshBase<MeshType>& mesh );

/**
 * @brief Overload to be called by isUniform(const MeshBase<MeshType>&),
 * provided for testing purposes.
 * @overload
 * 
 * Chains call to isRectangular(
	const MeshBase<MeshType>&,
	const Ref<const VectorXs>&,
	const Ref<const VectorXs>&,
	const std::vector<IdxVector>&)
 */
template<typename MeshType>
bool isUniform(
	const MeshBase<MeshType>& mesh,
	const Ref<const VectorXs>& xCoords,
	const Ref<const VectorXs>& yCoords,
	const std::vector<IdxVector>& edgesAtVertices = {} );



StructMesh meshToStructMesh(
	Mesh&&,
	const std::vector<IdxVector>& edgesAtVertex,
	const IdxVector& corners
);



bool optimiseMeshType( MeshVariant& );

} // end namespace hms

#include "meshTypeFunctions.tpp"

#endif